﻿using Microsoft.ML;
using Microsoft.ML.Core.Data;
using Microsoft.ML.Runtime.Data;
using Spear.Tests.Client.ML.Models;
using System;
using System.IO;
using Microsoft.ML.Transforms.Normalizers;

namespace Spear.Tests.Client.ML
{
    /// <summary> 出租车费用预测 </summary>
    public class TaxiFarePrediction
    {
        private static readonly string TrainDataPath = Path.Combine(Directory.GetCurrentDirectory(), "ML/data", "taxi-fare-train.csv");
        private static readonly string TestDataPath = Path.Combine(Directory.GetCurrentDirectory(), "ML/data", "taxi-fare-test.csv");
        private static readonly string ModelPath = Path.Combine(Directory.GetCurrentDirectory(), "ML/data", "Model.zip");
        private static TextLoader _textLoader;

        public static void Start(string[] args)
        {
            Console.WriteLine(Environment.CurrentDirectory);

            // <Snippet3>
            MLContext mlContext = new MLContext(0);
            // </Snippet3>

            // <Snippet4>
            _textLoader = mlContext.Data.TextReader(new TextLoader.Arguments()
            {
                Separator = ",",
                HasHeader = true,
                Column = new[]
                            {
                                new TextLoader.Column("VendorId", DataKind.Text, 0),
                                new TextLoader.Column("RateCode", DataKind.Text, 1),
                                new TextLoader.Column("PassengerCount", DataKind.R4, 2),
                                new TextLoader.Column("TripTime", DataKind.R4, 3),
                                new TextLoader.Column("TripDistance", DataKind.R4, 4),
                                new TextLoader.Column("PaymentType", DataKind.Text, 5),
                                new TextLoader.Column("FareAmount", DataKind.R4, 6)
                            }
            }
            );
            // </Snippet4>

            // <Snippet5>
            var model = Train(mlContext, TrainDataPath);
            // </Snippet5>

            // <Snippet14>
            Evaluate(mlContext, model);
            // </Snippet14>

            // <Snippet20>
            TestSinglePrediction(mlContext);
            // </Snippet20>
        }

        /// <summary> 机器训练 </summary>
        /// <param name="mlContext"></param>
        /// <param name="dataPath"></param>
        /// <returns></returns>
        public static ITransformer Train(MLContext mlContext, string dataPath)
        {
            var dataView = _textLoader.Read(dataPath);
            var pipeline = mlContext.Transforms.CopyColumns("FareAmount", "Label")
                //.Append(mlContext.Transforms.Categorical.OneHotEncoding("VendorId", "VendorIdEncoded"))
                //.Append(mlContext.Transforms.Categorical.OneHotEncoding("RateCode", "RateCodeEncoded"))
                //.Append(mlContext.Transforms.Categorical.OneHotEncoding("PaymentType", "PaymentTypeEncoded"))
                //.Append(mlContext.Transforms.Normalize("PassengerCount",
                //    mode: NormalizingEstimator.NormalizerMode.MeanVariance))
                //.Append(mlContext.Transforms.Normalize("TripTime",
                //    mode: NormalizingEstimator.NormalizerMode.MeanVariance))
                //.Append(mlContext.Transforms.Normalize("TripDistance",
                //    mode: NormalizingEstimator.NormalizerMode.MeanVariance))
                //.Append(mlContext.Transforms.Concatenate("Features", "VendorIdEncoded", "RateCodeEncoded",
                //    "PaymentTypeEncoded", "PassengerCount", "TripTime", "TripDistance"))
                //.Append(mlContext.Regression.Trainers.StochasticDualCoordinateAscent());
                .Append(mlContext.Transforms.Categorical.OneHotEncoding("VendorId"))
                .Append(mlContext.Transforms.Categorical.OneHotEncoding("RateCode"))
                .Append(mlContext.Transforms.Categorical.OneHotEncoding("PaymentType"))
                .Append(mlContext.Transforms.Normalize("PassengerCount",
                    mode: NormalizingEstimator.NormalizerMode.MeanVariance))
                .Append(mlContext.Transforms.Normalize("TripTime",
                    mode: NormalizingEstimator.NormalizerMode.MeanVariance))
                .Append(mlContext.Transforms.Normalize("TripDistance",
                    mode: NormalizingEstimator.NormalizerMode.MeanVariance))
                .Append(mlContext.Transforms.Concatenate("Features", "VendorId", "RateCode", "PassengerCount",
                    "TripTime", "TripDistance", "PaymentType"))

                .Append(mlContext.Regression.Trainers.FastTree(learningRate: 0.3D));


            Console.WriteLine("=============== Create and Train the Model ===============");

            // <Snippet11>
            var model = pipeline.Fit(dataView);
            // </Snippet11>

            Console.WriteLine("=============== End of training ===============");
            Console.WriteLine();
            // <Snippet12>
            SaveModelAsFile(mlContext, model);
            return model;
            // </Snippet12>
        }

        /// <summary> 评估 </summary>
        /// <param name="mlContext"></param>
        /// <param name="model"></param>
        private static void Evaluate(MLContext mlContext, ITransformer model)
        {
            // <Snippet15>
            var dataView = _textLoader.Read(TestDataPath);
            // </Snippet15>

            // <Snippet16>
            var predictions = model.Transform(dataView);
            // </Snippet16>
            // <Snippet17>
            var metrics = mlContext.Regression.Evaluate(predictions, "Label", "Score");
            // </Snippet17>

            Console.WriteLine();
            Console.WriteLine($"*************************************************");
            Console.WriteLine($"*       Model quality metrics evaluation         ");
            Console.WriteLine($"*------------------------------------------------");
            // <Snippet18>
            Console.WriteLine($"*       R2 Score:      {metrics.RSquared:0.##}");
            // </Snippet18>
            // <Snippet19>
            Console.WriteLine($"*       RMS loss:      {metrics.Rms:#.##}");
            // </Snippet19>
            Console.WriteLine($"*************************************************");

        }

        private static void TestSinglePrediction(MLContext mlContext)
        {
            //load the model
            // <Snippet21>
            ITransformer loadedModel;
            using (var stream = new FileStream(ModelPath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                loadedModel = mlContext.Model.Load(stream);
            }
            // </Snippet21>

            //Prediction test
            // Create prediction function and make prediction.
            // <Snippet22>
            var predictionFunction = loadedModel.MakePredictionFunction<TaxiTrip, TaxiTripFarePrediction>(mlContext);
            // </Snippet22>
            //Sample: 
            //vendor_id,rate_code,passenger_count,trip_time_in_secs,trip_distance,payment_type,fare_amount
            //VTS,1,1,1140,3.75,CRD,15.5
            // <Snippet23>
            var taxiTripSample = new TaxiTrip()
            {
                VendorId = "VTS",
                RateCode = "1",
                PassengerCount = 1,
                TripTime = 1140,
                TripDistance = 3.75f,
                PaymentType = "CRD",
                FareAmount = 0 // To predict. Actual/Observed = 15.5
            };
            // </Snippet23>
            // <Snippet24>
            var prediction = predictionFunction.Predict(taxiTripSample);
            // </Snippet24>
            // <Snippet25>
            Console.WriteLine($"**********************************************************************");
            Console.WriteLine($"Predicted fare: {prediction.FareAmount:0.####}, actual fare: 15.5");
            Console.WriteLine($"**********************************************************************");
            // </Snippet25>

            taxiTripSample = new TaxiTrip
            {
                VendorId = "VTS",
                RateCode = "1",
                PassengerCount = 1,
                TripDistance = 10.33f,
                PaymentType = "CSH",
                FareAmount = 0 // predict it. actual = 29.5
            };
            // </Snippet23>
            // <Snippet24>
            prediction = predictionFunction.Predict(taxiTripSample);
            // </Snippet24>
            // <Snippet25>
            Console.WriteLine($"**********************************************************************");
            Console.WriteLine($"Predicted fare: {prediction.FareAmount:0.####}, actual fare: 29.5");
            Console.WriteLine($"**********************************************************************");
        }

        private static void SaveModelAsFile(MLContext mlContext, ITransformer model)
        {
            // <Snippet13> 
            using (var fileStream = new FileStream(ModelPath, FileMode.Create, FileAccess.Write, FileShare.Write))
                mlContext.Model.Save(model, fileStream);
            // </Snippet13>

            Console.WriteLine("The model is saved to {0}", ModelPath);
        }
    }
}
